package top.z.orm.handle.osql.chain;

import top.z.orm.core.osql.Op;
import top.z.orm.core.osql.OrderType;
import top.z.orm.core.osql.chain.Select;

import java.util.Arrays;
import java.util.StringJoiner;

import static top.z.orm.core.osql.SqlKeyWord.*;

public class SelectImpl extends AbstractSelectToSql implements Select {

    private boolean isAddAnd       = false;
    private boolean isStartOn      = true;
    private boolean isStartWhere   = true;
    private boolean isStartField   = true;
    private boolean isStartGroupBy = true;
    private boolean isStartHaving  = true;
    private boolean isStartOrderBy = true;

    @Override
    public Select select() {
        if (isStartField) {
            sql.add(SELECT).add(STAR);
            isStartField = false;
        }
        return this;
    }

    @Override
    public Select select(String column) {
        if (isStartField) {
            sql.add(SELECT).add(column);
            isStartField = false;
        } else {
            sql.add(COMMA);
            sql.add(column);
        }
        return this;
    }

    @Override
    public Select distinct(String field) {
        // DISTINCT(field)
        return select(DISTINCT + L_PARENTHESIS + field + R_PARENTHESIS);
    }

    @Override
    public Select from(String table) {
        sql.add(FROM).add(table);
        return this;
    }

    @Override
    public Select join(String anotherTable) {
        sql.add(JOIN).add(anotherTable);
        return this;
    }

    @Override
    public Select innerJoin(String anotherTable) {
        sql.add(INNER_JOIN).add(anotherTable);
        return this;
    }

    @Override
    public Select leftJoin(String anotherTable) {
        sql.add(LEFT_JOIN).add(anotherTable);
        return this;
    }

    @Override
    public Select rightJoin(String anotherTable) {
        sql.add(RIGHT_JOIN).add(anotherTable);
        return this;
    }

    @Override
    public Select on() {
        sql.add(ON);
        isStartOn = false;
        return this;
    }

    @Override
    public Select on(String expression) {
        if (isStartOn) {
            sql.add(ON).add(expression);
            isStartOn = false;
        } else {
            if (isAddAnd) {
                sql.add(AND);
            }
            sql.add(expression);
            isAddAnd = true;
        }
        return this;
    }

    @Override
    public Select between(String field, Number low, Number high) {
        if (isAddAnd) {
            sql.add(AND);
        }
        sql.add(field).add(BETWEEN).add(String.valueOf(low)).add(AND).add(String.valueOf(high));
        isAddAnd = true;
        return this;
    }

    @Override
    public Select notBetween(String field, Number low, Number high) {
        if (isAddAnd) {
            sql.add(AND);
        }
        sql.add(field).add(NOT_BETWEEN).add(String.valueOf(low)).add(AND).add(String.valueOf(high));
        isAddAnd = true;
        return this;
    }

    @Override
    public Select isNull(String field) {
        if (isAddAnd) {
            sql.add(AND);
        }
        sql.add(field).add(IS_NULL);
        return this;
    }

    @Override
    public Select isNotNull(String field) {
        if (isAddAnd) {
            sql.add(AND);
        }
        sql.add(field).add(IS_NOT_NULL);
        return this;
    }

    @Override
    public Select in(String field, Number... value) {
        return inOrNotIn(field, IN, value);
    }

    public Select notIn(String field, Number... value) {
        return inOrNotIn(field, NOT_IN, value);
    }

    private Select inOrNotIn(String field, String op, Number... valueList) {
        if (isAddAnd) {
            sql.add(AND);
        }
        StringJoiner value = new StringJoiner(COMMA);
        Arrays.asList(valueList).forEach(i -> value.add(String.valueOf(i)));
        // eg: in ( 99,18 )
        sql.add(field).add(op).add(L_PARENTHESIS).add(value.toString()).add(R_PARENTHESIS);
        return this;
    }

    @Override
    public Select in(String field, String value) {
        return inOrNotIn(field, IN, value);
    }

    @Override
    public Select notIn(String field, String value) {
        return inOrNotIn(field, NOT_IN, value);
    }

    private Select inOrNotIn(String field, String op, String value) {
        if (isAddAnd) {
            sql.add(AND);
        }
        value = value.replace(COMMA, "'" + COMMA + "'");
        value = "'" + value + "'";
        // in ('client01','bee')
        sql.add(field).add(op).add(L_PARENTHESIS).add(value).add(R_PARENTHESIS);
        return this;
    }

    @Override
    public Select groupBy(String field) {
        if (isStartGroupBy) {
            sql.add(GROUP_BY).add(field);
            isStartGroupBy = false;
        } else {
            sql.add(COMMA).add(field);
        }
        return this;
    }

    @Override
    public Select having(String expression) {
        if (isStartHaving) {
            sql.add(HAVING).add(expression);
            isStartHaving = false;
        } else {
            sql.add(AND).add(expression);
        }
        return this;
    }

    @Override
    public Select orderBy(String field) {
        if (isStartOrderBy) {
            sql.add(ORDER_BY).add(field);
            isStartOrderBy = false;
        } else {
            sql.add(COMMA).add(field);
        }
        return this;
    }

    @Override
    public Select orderBy(String field, OrderType orderType) {
        if (isStartOrderBy) {
            sql.add(ORDER_BY).add(field).add(orderType.getName());
            isStartOrderBy = false;
        } else {
            sql.add(COMMA).add(field).add(orderType.getName());
        }
        return this;
    }

    private Select useSubSelect(String keyword, String subSelect) {
        // in ,exists
        sql.add(keyword).add(L_PARENTHESIS).add(subSelect).add(R_PARENTHESIS);
        return this;
    }

    @Override
    public Select exists(Select subSelect) {
        return exists(subSelect.toSQL(true));
    }

    public Select exists(String subSelect) {
        return useSubSelect("", subSelect);
    }

    @Override
    public Select notExists(Select subSelect) {
        return notExists(subSelect.toSQL(true));
    }

    @Override
    public Select notExists(String subSelect) {
        return useSubSelect(NOT_EXISTS, subSelect);
    }

    @Override
    public Select in(Select subSelect) {
        return in(subSelect.toSQL(true));
    }

    @Override
    public Select in(String subSelect) {
        return useSubSelect(IN, subSelect);
    }

    @Override
    public Select notIn(Select subSelect) {
        return notIn(subSelect.toSQL(true));
    }

    @Override
    public Select notIn(String subSelect) {
        return useSubSelect(NOT_IN, subSelect);
    }

    @Override
    public Select start(int start) {
        this.start = start;
        return this;
    }

    @Override
    public Select size(int size) {
        this.size = size;
        return this;
    }

    @Override
    public Select lParenthesis() {
        sql.add(L_PARENTHESIS);
        return this;
    }

    @Override
    public Select rParenthesis() {
        sql.add(R_PARENTHESIS);
        return this;
    }

    @Override
    public Select where() {
        sql.add(WHERE);
        isStartWhere = false;
        return this;
    }

    @Override
    public Select where(String expression) {
        if (isStartWhere) {
            sql.add(WHERE).add(expression);
            isStartWhere = false;
            isAddAnd = true;
        } else {
            if (isAddAnd) {
                sql.add(AND);
            }
            sql.add(expression);
            isAddAnd = true;
        }
        return this;
    }

    @Override
    public Select op(String field, Op opType, String value) {
        if (opType == Op.in) {
            return in(field, value);
        }
        if (opType == Op.notIn) {
            return notIn(field, value);
        }
        if (isAddAnd) sql.add(AND).add(field).add(opType.getOperator()).add("'").add(value).add("'");
        isAddAnd = true;
        return this;
    }

    @Override
    public Select op(String field, Op opType, Number value) {
        if (opType == Op.in) {
            return in(field, value);
        }
        if (opType == Op.notIn) {
            return notIn(field, value);
        }
        if (isAddAnd) {
            sql.add(AND);
        }
        sql.add(field).add(opType.getOperator()).add(String.valueOf(value));
        isAddAnd = true;
        return this;
    }

    @Override
    public Select op(String field, String value) {
        return op(field, Op.eq, value);
    }

    @Override
    public Select op(String field, Number value) {
        return op(field, Op.eq, value);
    }

    @Override
    public Select and() {
        sql.add(AND);
        isAddAnd = false;
        return this;
    }

    @Override
    public Select or() {
        sql.add(OR);
        isAddAnd = false;
        return this;
    }
}